// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package com.amazon.ion;

import com.amazon.ion.impl._Private_IonConstants;

/**
 * Configures buffers that hold Ion data.
 */
public final class IonBufferConfiguration extends BufferConfiguration<IonBufferConfiguration> {

    /**
     * The standard configuration. This will be used unless the user chooses custom settings.
     */
    public static final IonBufferConfiguration DEFAULT =
        IonBufferConfiguration.Builder.standard().build();

    /**
     * Functional interface for handling oversized symbol tables.
     */
    @FunctionalInterface
    public interface OversizedSymbolTableHandler {
        /**
         * Invoked when the user specifies a finite maximum buffer size and that size is exceeded by symbol table(s)
         * alone. Because symbol tables cannot be truncated without corrupting values that follow in the stream,
         * this condition is not recoverable.
         */
        void onOversizedSymbolTable();
    }

    /**
     * Builds IonBufferConfiguration instances.
     */
    public static final class Builder extends BufferConfiguration.Builder<IonBufferConfiguration, Builder> {

        /**
         * 4-byte IVM + 1 byte user value.
         */
        private static final int MINIMUM_MAX_VALUE_SIZE = 5;

        private static void throwDueToUnexpectedOversizedValue() {
            throw new IonException("An oversized value was found even though no maximum size was configured. " +
                "This is either due to data corruption or encountering a scalar larger than 2 GB. In the latter case, " +
                "an IonBufferConfiguration can be configured to allow the reader to skip the oversized scalar."
            );
        }

        private static final OversizedValueHandler THROWING_OVERSIZED_VALUE_HANDLER = Builder::throwDueToUnexpectedOversizedValue;
        private static final OversizedSymbolTableHandler THROWING_OVERSIZED_SYMBOL_TABLE_HANDLER = Builder::throwDueToUnexpectedOversizedValue;

        /**
         * An OversizedValueHandler that does nothing.
         */
        private static final OversizedValueHandler NO_OP_OVERSIZED_VALUE_HANDLER = () -> {
            // If no maximum buffer size is configured, values cannot be considered oversized and this
            // implementation will never be called.
            // If a maximum buffer size is configured, a handler must also be configured. In that case,
            // this implementation will only be called if the user provides it to the builder manually.
        };

        /**
         * A DataHandler that does nothing.
         */
        private static final DataHandler NO_OP_DATA_HANDLER = bytes -> {
            // Do nothing.
        };

        /**
         * An OversizedSymbolTableHandler that does nothing.
         */
        private static final OversizedSymbolTableHandler NO_OP_OVERSIZED_SYMBOL_TABLE_HANDLER = () -> {
            // If no maximum buffer size is configured, symbol tables cannot be considered oversized and this
            // implementation will never be called.
            // If a maximum buffer size is configured, a handler must also be configured. In that case,
            // this implementation will only be called if the user provides it to the builder manually.
        };

        /**
         * The handler that will be notified when oversized symbol tables are encountered.
         */
        private OversizedSymbolTableHandler oversizedSymbolTableHandler = null;

        private Builder() {
            // Must be publicly instantiated via the factory method.
        }

        /**
         * Provides the standard builder with the default initial buffer size, an unbounded maximum buffer size, and
         * an event handler that does nothing.
         * @return a standard Builder.
         */
        public static Builder standard() {
            return new Builder();
        }

        /**
         * Provides a new builder that would build IonBufferConfiguration instances with configuration identical to
         * the given configuration.
         * @param existingConfiguration an existing configuration.
         * @return a new mutable builder.
         */
        public static Builder from(IonBufferConfiguration existingConfiguration) {
            return IonBufferConfiguration.Builder.standard()
                .onData(existingConfiguration.getDataHandler())
                .onOversizedValue(existingConfiguration.getOversizedValueHandler())
                .onOversizedSymbolTable(existingConfiguration.getOversizedSymbolTableHandler())
                .withInitialBufferSize(existingConfiguration.getInitialBufferSize())
                .withMaximumBufferSize(existingConfiguration.getMaximumBufferSize());
        }

        /**
         * Sets the handler that will be notified when oversized symbol tables are encountered. If the maximum buffer
         * size is finite (see {@link #withMaximumBufferSize(int)}, this handler is required to be non-null.
         *
         * @param handler the handler.
         * @return this builder.
         */
        public Builder onOversizedSymbolTable(OversizedSymbolTableHandler handler) {
            oversizedSymbolTableHandler = handler;
            return this;
        }

        /**
         * @return the handler that will be notified when oversized symbol tables are encountered.
         */
        public OversizedSymbolTableHandler getOversizedSymbolTableHandler() {
            return oversizedSymbolTableHandler;
        }

        @Override
        public int getMinimumMaximumBufferSize() {
            return MINIMUM_MAX_VALUE_SIZE;
        }

        @Override
        public OversizedValueHandler getNoOpOversizedValueHandler() {
            return NO_OP_OVERSIZED_VALUE_HANDLER;
        }

        @Override
        public OversizedValueHandler getThrowingOversizedValueHandler() {
            return THROWING_OVERSIZED_VALUE_HANDLER;
        }

        @Override
        public DataHandler getNoOpDataHandler() {
            return NO_OP_DATA_HANDLER;
        }

        /**
         * @return an OversizedSymbolTableHandler that does nothing.
         */
        public OversizedSymbolTableHandler getNoOpOversizedSymbolTableHandler() {
            return NO_OP_OVERSIZED_SYMBOL_TABLE_HANDLER;
        }

        /**
         * @return an OversizedSymbolTableHandler that always throws {@link IonException}.
         */
        public OversizedSymbolTableHandler getThrowingOversizedSymbolTableHandler() {
            return THROWING_OVERSIZED_SYMBOL_TABLE_HANDLER;
        }

        @Override
        public IonBufferConfiguration build() {
            return new IonBufferConfiguration(this);
        }
    }

    /**
     * The handler that will be notified when oversized symbol tables are encountered.
     */
    private final OversizedSymbolTableHandler oversizedSymbolTableHandler;

    /**
     * Constructs an instance from the given Builder.
     * @param builder the builder containing the settings to apply to the new configuration.
     */
    private IonBufferConfiguration(Builder builder) {
        super(builder);
        if (builder.getOversizedSymbolTableHandler() == null) {
            requireMaximumBufferSize();
            oversizedSymbolTableHandler = builder.getThrowingOversizedSymbolTableHandler();
        } else {
            oversizedSymbolTableHandler = builder.getOversizedSymbolTableHandler();
        }
    }

    /**
     * Requires that the maximum buffer size not be limited.
     */
    private void requireMaximumBufferSize() {
        if (getMaximumBufferSize() < _Private_IonConstants.ARRAY_MAXIMUM_SIZE) {
            throw new IllegalArgumentException(
                "Must specify both an OversizedValueHandler and OversizedSymbolTableHandler when a custom maximum buffer size is specified."
            );
        }
    }

    /**
     * @return the handler that will be notified when oversized symbol tables are encountered.
     */
    public OversizedSymbolTableHandler getOversizedSymbolTableHandler() {
        return oversizedSymbolTableHandler;
    }
}
